﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.CodeDom.Compiler;
using System.Security.Permissions;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Web.Services.Description;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using Binding = System.ServiceModel.Channels.Binding;

namespace WU.LoadTester.Lib.Service
{
    public static class DynamicProxy
    {
        [SecurityPermission(SecurityAction.Demand)]
        public static object GetClientClass(string url, EndpointAddress address, Binding binding)
        {
            try
            {
                //Read the Service description from the URL
                Uri uri;
                Uri.TryCreate(url + "?wsdl", UriKind.Absolute, out uri);
                var client = new MetadataExchangeClient(uri, MetadataExchangeClientMode.HttpGet);
                client.ResolveMetadataReferences = true;
                var metadata = client.GetMetadata();

                //Generate contract
                var importer = new WsdlImporter(metadata);
                var generator = new ServiceContractGenerator();
                generator.Options = ServiceContractGenerationOptions.TaskBasedAsynchronousMethod | ServiceContractGenerationOptions.ClientClass | ServiceContractGenerationOptions.ChannelInterface;
                
                var contracts = importer.ImportAllContracts();

                //Generate and get a list of possible service names
                var services = new List<string>();              
                
                //Get service name from url
                string serviceName = url.Split('/').Last().Split('.').First() + "Client";
                services.Add(serviceName);

                foreach (ContractDescription contract in contracts)
                {
                    var name = contract.Name.StartsWith("I") ? contract.Name.Substring(1) : contract.Name;
                    generator.GenerateServiceContractType(contract);
                    services.Add(name + "Client");
                }

                if (generator.Errors.Count > 0)
                    throw new Exception(Resources.Error_EndpointInvalid);

                var codeProvider = CodeDomProvider.CreateProvider("CSharp");
                var references = new string[]
                                     {
                                         "System.dll", "System.Web.dll", "System.Xml.dll", "System.Data.dll",
                                         "System.Web.Services.dll"
                                     };
                var parameters = new CompilerParameters(references);
                var result = codeProvider.CompileAssemblyFromDom(parameters, generator.TargetCompileUnit);

                if(result.Errors.Count > 0)
                    throw new Exception(String.Format(Resources.Error_ServiceClientCompilation, result.Errors.Cast<CompilerError>().Select(e => e.ErrorText).Aggregate((t1,t2) => t1 + "\n" + t2)));


                //Load the compiled assembly and the client object
                try
                {
                    Assembly.LoadFile(result.PathToAssembly);
                    
                    //Loop through the contracts generated and try to find the client
                    Type type = null;
                    foreach (var service in services)
                    {
                        type = result.CompiledAssembly.GetType(service);
                        if (type != null && type.BaseType != null && type.BaseType.GetGenericTypeDefinition() == typeof(ClientBase<>))
                            break;
                    }
                    
                    //Not found, loop through all compiled types
                    if (type == null)
                    {
                        var types = result.CompiledAssembly.GetTypes();
                        foreach (var compiledType in types)
                        {
                            if (compiledType.BaseType != null && compiledType.BaseType.GetGenericTypeDefinition() == typeof(ClientBase<>))
                            {
                                type= compiledType;
                                break;
                            }
                        }
                        //No client generated?
                        if(type == null)
                            throw new Exception(Resources.Error_ClientGenerationNotFound);
                    }
                    
                    object serviceClass = Activator.CreateInstance(type, new object[] { binding, address });
                    return serviceClass;
                }
                catch (Exception ex)
                {
                    throw new Exception(Resources.Error_AssemblyLoadFailure, ex);
                }
            }
            catch(System.Net.WebException netEx)
            {
                throw new Exception(Resources.Error_AddressInvalid, netEx);
            }
        }
    }
}
